// 版权所有2012 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package net

import (
	"context"
	"internal/nettrace"
	"internal/singleflight"
	"sync"
)

// 协议包含internet协议之间的最小映射
// 没有完整
// 协议编号列表的平台的名称和编号。
// 
// 见https:
// 
// 在Unix上，此映射通过lookupProtocol由ReadProtocol扩展。
var protocols = map[string]int{
	"icmp":      1,
	"igmp":      2,
	"tcp":       6,
	"udp":       17,
	"ipv6-icmp": 58,
}

// 对于没有完整端口号列表的平台，服务包含服务名称和端口
// 号之间的最小映射。
// 
// 参见https:
// 
// 在Unix上，此地图通过goLookupPort的readServices进行了扩充。
var services = map[string]map[string]int{
	"udp": {
		"domain": 53,
	},
	"tcp": {
		"ftp":    21,
		"ftps":   990,
		"gopher": 70, // ʕ◔ϖ◔ʔ
		"http":   80,
		"https":  443,
		"imap2":  143,
		"imap3":  220,
		"imaps":  993,
		"pop3":   110,
		"pop3s":  995,
		"smtp":   25,
		"ssh":    22,
		"telnet": 23,
	},
}

// 测试可以使用dnsWaitGroup等待所有DNS goroutine 
// 完成。这样可以避免在测试挂钩上进行比赛。
var dnsWaitGroup sync.WaitGroup

const maxProtoLength = len("RSVP-E2E-IGNORE") + 10 // 有增长空间

func lookupProtocolMap(name string) (int, error) {
	var lowerProtocol [maxProtoLength]byte
	n := copy(lowerProtocol[:], name)
	lowerASCIIBytes(lowerProtocol[:n])
	proto, found := protocols[string(lowerProtocol[:n])]
	if !found || n != len(name) {
		return 0, &AddrError{Err: "unknown IP protocol specified", Addr: name}
	}
	return proto, nil
}

// maxPortBufSize是服务最长的合理名称
// （非数字端口）。
// 目前已知最长的IANA未注册名称是
// “mobility header”，因此我们使用该长度，加上一些斜率，以防
// 将来添加更长的名称。
const maxPortBufSize = len("mobility-header") + 10

func lookupPortMap(network, service string) (port int, error error) {
	switch network {
	case "tcp4", "tcp6":
		network = "tcp"
	case "udp4", "udp6":
		network = "udp"
	}

	if m, ok := services[network]; ok {
		var lowerService [maxPortBufSize]byte
		n := copy(lowerService[:], service)
		lowerASCIIBytes(lowerService[:n])
		if port, ok := m[string(lowerService[:n])]; ok && n == len(service) {
			return port, nil
		}
	}
	return 0, &AddrError{Err: "unknown port", Addr: network + "/" + service}
}

// ipVersion返回所提供网络的IP版本：“4”、“6”或0 
// 如果网络未以“4”或“6”字节结尾。
func ipVersion(network string) byte {
	if network == "" {
		return 0
	}
	n := network[len(network)-1]
	if n != '4' && n != '6' {
		n = 0
	}
	return n
}

// DefaultResolver是包级查找
// 函数和未指定解析程序的拨号程序使用的解析程序。
var DefaultResolver = &Resolver{}

// 解析程序查找名称和数字。
// 
// 零*解析器相当于零解析器。
type Resolver struct {
	// PreferGo控制Go的内置DNS解析程序在可用的平台上是否是首选
	// 。它相当于设置
	// GODEBUG=netdns=go，但范围仅限于此解析器。
	PreferGo bool

	// StreetErrors控制使用
	// （包括超时、套接字错误和SERVFAIL）。对于由多个
	// Go内置解析程序时临时错误的行为
	// 子查询组成的查询（如a+AAAA地址查找，或遍历
	// DNS搜索列表），此选项会导致此类错误中止整个查询，而不是返回部分结果。默认情况下，这是
	// 未启用，因为它可能会影响
	// 与错误处理AAAA查询的解析程序的兼容性。
	StrictErrors bool

	// Dial可选指定一个备用拨号程序，供
	// Go的内置DNS解析程序使用，以建立到DNS服务的TCP和UDP连接
	// 。address参数中的主机将始终是文本IP地址而不是主机名，address参数中的端口将是文本端口号而不是服务名。
	// 如果返回的Conn也是PacketConn，则发送和接收的DNS 
	// 消息必须遵守RFC 1035第4.2.1节“UDP使用”。否则，通过Conn传输的DNS消息必须符合RFC 7766第5节“传输协议选择”的要求。
	// 如果为零，则使用默认拨号程序。
	Dial func(ctx context.Context, network, address string) (Conn, error)

	// lookupGroup将LookupIPAddress调用合并在一起，以查找同一个
	// 主机。lookupGroup键是LookupIPAddr.host参数。
	// 返回值为（[]IPAddr，错误）。
	lookupGroup singleflight.Group

	// TODO（bradfitz）：可选接口impl override hook 
	// TODO（bradfitz）：超时时间。持续时间？
}

func (r *Resolver) preferGo() bool     { return r != nil && r.PreferGo }
func (r *Resolver) strictErrors() bool { return r != nil && r.StrictErrors }

func (r *Resolver) getLookupGroup() *singleflight.Group {
	if r == nil {
		return &DefaultResolver.lookupGroup
	}
	return &r.lookupGroup
}

// LookupHost使用本地解析器查找给定主机。
// 它返回该主机地址的一部分。
// 
// LookupHost在内部使用context.Background；要指定上下文，请使用
// Resolver.LookupHost。
func LookupHost(host string) (addrs []string, err error) {
	return DefaultResolver.LookupHost(context.Background(), host)
}

// LookupHost使用本地解析器查找给定主机。
// 它返回该主机地址的一部分。
func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error) {
	// 确保无论我们以后做什么，主机==“”都被拒绝。例如，
	// parseIP不接受空字符串。
	if host == "" {
		return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
	}
	if ip, _ := parseIPZone(host); ip != nil {
		return []string{host}, nil
	}
	return r.lookupHost(ctx, host)
}

// LookupIP使用本地解析器查找主机。
// 它返回该主机的IPv4和IPv6地址的一部分。
func LookupIP(host string) ([]IP, error) {
	addrs, err := DefaultResolver.LookupIPAddr(context.Background(), host)
	if err != nil {
		return nil, err
	}
	ips := make([]IP, len(addrs))
	for i, ia := range addrs {
		ips[i] = ia.IP
	}
	return ips, nil
}

// LookupIPAddr使用本地解析器查找主机。
// 它返回该主机的IPv4和IPv6地址的一部分。
func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, error) {
	return r.lookupIPAddr(ctx, "ip", host)
}

// LookupIP使用本地解析器查找给定网络的主机。
// 返回该主机IP地址的一个片段，其类型由
// 网络指定。
// 网络必须是“ip”、“ip4”或“ip6”之一。
func (r *Resolver) LookupIP(ctx context.Context, network, host string) ([]IP, error) {
	afnet, _, err := parseNetwork(ctx, network, false)
	if err != nil {
		return nil, err
	}
	switch afnet {
	case "ip", "ip4", "ip6":
	default:
		return nil, UnknownNetworkError(network)
	}
	addrs, err := r.internetAddrList(ctx, afnet, host)
	if err != nil {
		return nil, err
	}
	ips := make([]IP, 0, len(addrs))
	for _, addr := range addrs {
		ips = append(ips, addr.(*IPAddr).IP)
	}
	return ips, nil
}

// onlyValuesCtx是一种在基础上下文尚未过期时使用基础上下文
// 进行值查找的上下文。
type onlyValuesCtx struct {
	context.Context
	lookupValues context.Context
}

var _ context.Context = (*onlyValuesCtx)(nil)

// 如果原始上下文未过期，则值将执行查找。
func (ovc *onlyValuesCtx) Value(key interface{}) interface{} {
	select {
	case <-ovc.lookupValues.Done():
		return nil
	default:
		return ovc.lookupValues.Value(key)
	}
}

// WithUnexpiredValuesReserved返回一个上下文。仅使用lookupCtx 
// 作为其值的上下文，否则它永远不会被取消，也没有截止日期。
// 如果查找上下文过期，则任何查找的值都将返回nil。
// 见第28600期。
func withUnexpiredValuesPreserved(lookupCtx context.Context) context.Context {
	return &onlyValuesCtx{Context: context.Background(), lookupValues: lookupCtx}
}

// lookupIPAddr使用本地解析器和特定网络查找主机。
// 它返回该主机的IPv4和IPv6地址的一部分。
func (r *Resolver) lookupIPAddr(ctx context.Context, network, host string) ([]IPAddr, error) {
	// 确保无论我们以后做什么，主机==“”都被拒绝。例如，
	// parseIP不接受空字符串。
	if host == "" {
		return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
	}
	if ip, zone := parseIPZone(host); ip != nil {
		return []IPAddr{{IP: ip, Zone: zone}}, nil
	}
	trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace)
	if trace != nil && trace.DNSStart != nil {
		trace.DNSStart(host)
	}
	// 默认情况下，基础解析器func是lookupIP，但它可以被测试覆盖。这是net/http所需要的，因此
	// 使用上下文键而不是未报告的变量。
	resolverFunc := r.lookupIP
	if alt, _ := ctx.Value(nettrace.LookupIPAltResolverKey{}).(func(context.Context, string, string) ([]IPAddr, error)); alt != nil {
		resolverFunc = alt
	}

	// 我们不希望取消ctx会影响
	// 查找组操作。否则，如果我们的上下文被
	// 取消，则可能会导致使用完全不同的上下文返回到查找
	// 的错误。但是，我们需要保留
	// 仅上下文中的值。见第28600期。
	lookupGroupCtx, lookupGroupCancel := context.WithCancel(withUnexpiredValuesPreserved(ctx))

	lookupKey := network + "\000" + host
	dnsWaitGroup.Add(1)
	ch, called := r.getLookupGroup().DoChan(lookupKey, func() (interface{}, error) {
		defer dnsWaitGroup.Done()
		return testHookLookupIP(lookupGroupCtx, resolverFunc, network, host)
	})
	if !called {
		dnsWaitGroup.Done()
	}

	select {
	case <-ctx.Done():
		// 我们的上下文被取消。如果我们是唯一一个查找此键的
		// G程序，请从查找组中删除该键
		// ，然后取消查找。
		// 如果有其他goroutine在查找此密钥，
		// 让查找继续进行而不取消，稍后让
		// 使用相同密钥的查找共享结果。
		// 见第8602期、第20703期、第22724期。
		if r.getLookupGroup().ForgetUnshared(lookupKey) {
			lookupGroupCancel()
		} else {
			go func() {
				<-ch
				lookupGroupCancel()
			}()
		}
		err := mapErr(ctx.Err())
		if trace != nil && trace.DNSDone != nil {
			trace.DNSDone(nil, false, err)
		}
		return nil, err
	case r := <-ch:
		lookupGroupCancel()
		if trace != nil && trace.DNSDone != nil {
			addrs, _ := r.Val.([]IPAddr)
			trace.DNSDone(ipAddrsEface(addrs), r.Shared, r.Err)
		}
		return lookupIPReturn(r.Val, r.Err, r.Shared)
	}
}

// lookupIPReturn将singleflight.Do的返回值转换为
// LookupIP的返回值。
func lookupIPReturn(addrsi interface{}, err error, shared bool) ([]IPAddr, error) {
	if err != nil {
		return nil, err
	}
	addrs := addrsi.([]IPAddr)
	if shared {
		clone := make([]IPAddr, len(addrs))
		copy(clone, addrs)
		addrs = clone
	}
	return addrs, nil
}

// ipAddrsEface返回addrs的空接口片。
func ipAddrsEface(addrs []IPAddr) []interface{} {
	s := make([]interface{}, len(addrs))
	for i, v := range addrs {
		s[i] = v
	}
	return s
}

// LookupPort查找给定网络和服务的端口。
// 
// LookupPort在内部使用context.Background；要指定上下文，请使用
// Resolver.LookupPort。
func LookupPort(network, service string) (port int, err error) {
	return DefaultResolver.LookupPort(context.Background(), network, service)
}

// LookupPort查找给定网络和服务的端口。
func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) {
	port, needsLookup := parsePort(service)
	if needsLookup {
		switch network {
		case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
		case "": // Go 1.0未记录行为的提示通配符
			network = "ip"
		default:
			return 0, &AddrError{Err: "unknown network", Addr: network}
		}
		port, err = r.lookupPort(ctx, network, service)
		if err != nil {
			return 0, err
		}
	}
	if 0 > port || port > 65535 {
		return 0, &AddrError{Err: "invalid port", Addr: service}
	}
	return port, nil
}

// LookupCNAME返回给定主机的规范名称。
// 不关心规范名称的调用方可以直接调用
// LookupHost或LookupIP；两者都负责解析
// 作为查找的一部分的规范名称。
// 
// 规范名称是在零个
// 或多个CNAME记录之后的最终名称。如果主机不包含DNS“CNAME”记录，只要主机解析为
// 地址记录，则LookupCNAME不会返回错误。
// 
// 返回的规范名称已验证为正确的
// 格式化的表示格式域名。
// 
// LookupCNAME在内部使用context.Background；要指定上下文，请使用
// Resolver.LookupCNAME。
func LookupCNAME(host string) (cname string, err error) {
	return DefaultResolver.LookupCNAME(context.Background(), host)
}

// LookupCNAME返回给定主机的规范名称。
// 不关心规范名称的调用方可以直接调用
// LookupHost或LookupIP；两者都负责解析
// 作为查找的一部分的规范名称。
// 
// 规范名称是在零个
// 或多个CNAME记录之后的最终名称。如果主机不包含DNS“CNAME”记录，只要主机解析为
// 地址记录，则LookupCNAME不会返回错误。
// 
// 返回的规范名称已验证为正确的
// 格式化的表示格式域名。
func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, error) {
	cname, err := r.lookupCNAME(ctx, host)
	if err != nil {
		return "", err
	}
	if !isDomainName(cname) {
		return "", &DNSError{Err: errMalformedDNSRecordsDetail, Name: host}
	}
	return cname, nil
}

// LookupSRV尝试解析给定服务、协议和域名的SRV查询。协议是“tcp”或“udp”。
// 返回的记录按优先级排序，随机
// 按优先级内的权重排序。
// 
// LookupSRV构造要在RFC 2782之后查找的DNS名称。
// 也就是说，它查找服务。\u proto.name。为适应
// 以非标准名称发布SRV记录，如果服务
// 和proto都是空字符串，则LookupSRV直接查找名称。
// 
// 返回的服务名称已验证为正确的
// 格式化的表示格式域名。如果响应包含
// 无效名称，则会过滤掉这些记录，并在返回剩余结果（如果有）的同时返回错误
// 。
func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) {
	return DefaultResolver.LookupSRV(context.Background(), service, proto, name)
}

// LookupSRV尝试解析给定服务、
// 协议和域名的SRV查询。协议是“tcp”或“udp”。
// 返回的记录按优先级排序，随机
// 按优先级内的权重排序。
// 
// LookupSRV构造要在RFC 2782之后查找的DNS名称。
// 也就是说，它查找服务。\u proto.name。为了适应服务
// 以非标准名称发布SRV记录，如果服务
// 和proto都是空字符串，则LookupSRV直接查找名称。
// 
// 返回的服务名称已验证为正确的
// 格式化的表示格式域名。如果响应包含
// 无效名称，则会过滤掉这些记录，并在返回剩余结果（如果有）的同时返回错误
// 。
func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error) {
	cname, addrs, err := r.lookupSRV(ctx, service, proto, name)
	if err != nil {
		return "", nil, err
	}
	if cname != "" && !isDomainName(cname) {
		return "", nil, &DNSError{Err: "SRV header name is invalid", Name: name}
	}
	filteredAddrs := make([]*SRV, 0, len(addrs))
	for _, addr := range addrs {
		if addr == nil {
			continue
		}
		if !isDomainName(addr.Target) {
			continue
		}
		filteredAddrs = append(filteredAddrs, addr)
	}
	if len(addrs) != len(filteredAddrs) {
		return cname, filteredAddrs, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name}
	}
	return cname, filteredAddrs, nil
}

// LookupMX返回按首选项排序的给定域名的DNS MX记录。
// 
// 返回的邮件服务器名称已验证为正确的
// 格式化的表示格式域名。如果响应包含
// 无效名称，则会过滤掉这些记录，并在返回剩余结果（如果有）的同时返回错误
// 。
// 
// LookupMX在内部使用context.Background；要指定上下文，请使用
// Resolver.LookupMX。
func LookupMX(name string) ([]*MX, error) {
	return DefaultResolver.LookupMX(context.Background(), name)
}

// LookupMX返回按首选项排序的给定域名的DNS MX记录。
// 
// 返回的邮件服务器名称已验证为正确的
// 格式化的表示格式域名。如果响应包含
// 无效名称，则会过滤掉这些记录，并在返回剩余结果（如果有）的同时返回错误
// 。
func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error) {
	records, err := r.lookupMX(ctx, name)
	if err != nil {
		return nil, err
	}
	filteredMX := make([]*MX, 0, len(records))
	for _, mx := range records {
		if mx == nil {
			continue
		}
		// 跳过仅包含点的目标的主机名有效性检查，因为这用于表示“Null”MX记录。
		if mx.Host != "." && !isDomainName(mx.Host) {
			continue
		}
		filteredMX = append(filteredMX, mx)
	}
	if len(records) != len(filteredMX) {
		return filteredMX, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name}
	}
	return filteredMX, nil
}

// LookupNS返回给定域名的DNS NS记录。
// 
// 返回的名称服务器名称已验证为正确的
// 格式化的表示格式域名。如果响应包含
// 无效名称，则会过滤掉这些记录，并在返回剩余结果（如果有）的同时返回错误
// 。
// 
// LookupNS在内部使用context.Background；要指定上下文，请使用
// Resolver.LookupNS。
func LookupNS(name string) ([]*NS, error) {
	return DefaultResolver.LookupNS(context.Background(), name)
}

// LookupNS返回给定域名的DNS NS记录。
// 
// 返回的名称服务器名称已验证为正确的
// 格式化的表示格式域名。如果响应包含
// 无效名称，则会过滤掉这些记录，并在返回剩余结果（如果有）的同时返回错误
// 。
func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error) {
	records, err := r.lookupNS(ctx, name)
	if err != nil {
		return nil, err
	}
	filteredNS := make([]*NS, 0, len(records))
	for _, ns := range records {
		if ns == nil {
			continue
		}
		if !isDomainName(ns.Host) {
			continue
		}
		filteredNS = append(filteredNS, ns)
	}
	if len(records) != len(filteredNS) {
		return filteredNS, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name}
	}
	return filteredNS, nil
}

// LookupText返回给定域名的DNS TXT记录。
// 
// LookupText在内部使用context.Background；要指定上下文，请使用
// Resolver.lookuptext。
func LookupTXT(name string) ([]string, error) {
	return DefaultResolver.lookupTXT(context.Background(), name)
}

// LookupText返回给定域名的DNS TXT记录。
func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
	return r.lookupTXT(ctx, name)
}

// LookupAddr对给定地址执行反向查找，返回映射到该地址的名称列表
// 。
// 
// 返回的名称已验证为格式正确的表示格式
// 域名。如果响应包含无效的名称，则这些记录将被过滤出，并且将与剩余结果（如果有）一起返回一个错误。
// 
// 使用主机C库解析程序时，最多返回一个结果
// 。要绕过主机解析程序，请使用自定义解析程序。
// 
// LookupAddr在内部使用context.Background；要指定上下文，请使用
// Resolver.LookupAddr。
func LookupAddr(addr string) (names []string, err error) {
	return DefaultResolver.LookupAddr(context.Background(), addr)
}

// LookupAddr对给定地址执行反向查找，返回映射到该地址的名称列表
// 。
// 
// 返回的名称已验证为正确格式的表示格式
// 域名。如果响应包含无效的名称，则这些记录将被过滤出，并且将与剩余结果（如果有）一起返回一个错误。
func (r *Resolver) LookupAddr(ctx context.Context, addr string) ([]string, error) {
	names, err := r.lookupAddr(ctx, addr)
	if err != nil {
		return nil, err
	}
	filteredNames := make([]string, 0, len(names))
	for _, name := range names {
		if isDomainName(name) {
			filteredNames = append(filteredNames, name)
		}
	}
	if len(names) != len(filteredNames) {
		return filteredNames, &DNSError{Err: errMalformedDNSRecordsDetail, Name: addr}
	}
	return filteredNames, nil
}

// errMalformednsrecordsdetail是解析程序.Lookup。。。
// 方法接收包含无效DNS名称的DNS记录。这可能会与
// 结果一起返回，这些结果过滤掉了格式错误的记录。
var errMalformedDNSRecordsDetail = "DNS response contained records which contain invalid names"
